package IC.AST.SemanticAnalysis;

import IC.AST.*;
import IC.Error.ErrorStack;
import IC.Error.Error.ErrorType;
import IC.Error.ErrorStringIntervaled;
import IC.SymbolTables.Types.MethodType;
import IC.SymbolTables.Types.VoidType;
import IC.SymbolTables.Types.BasicType.BasicTypeEnum;

class TypeCheckVisitor extends Visitor 
{
	private IC.SymbolTables.Types.Type _funcRetType;
	private String _funcName, _classID;
	
	public Object Visit(ClassDeclaration node)
	{
		_classID = node.GetID();
		return null;
	}
	
	public Object Visit(Method node)
	{
		_funcName = node.GetID();
		_funcRetType = ((MethodType)node.GetType()).GetReturnType();
		return null;
	}
		
	public Object Visit(DeclorationStatement node)
	{
		if (node.GetExpression() != null && !CheckAssignment(node.GetASTType().GetType(), node.GetExpression()))
			ErrorStack.Push(
					new ErrorStringIntervaled(
							ErrorType.Semantic, 
							String.format("Can't initialize id '%s', incompatible types %s", 
									node.GetID(), LocationString()), 
							node.GetInterval()));
		
		return null;
	}
	
	public Object Visit(AssignStatement node)
	{
		if (!CheckAssignment(node.GetWhereTo().GetType(), node.GetWhat()))
			ErrorStack.Push(
					new ErrorStringIntervaled(
							ErrorType.Semantic, 
							"Can't assign, incompatible types" + LocationString(),
							node.GetInterval()));
		
		return null;
	}
	
	//return 110 :)
	private boolean CheckAssignment(IC.SymbolTables.Types.Type type, Expression expression) 
	{ 
		return expression.GetType().IsInstanceOf(type);
	}
	
	public Object Visit(ReturnStatement node)
	{
		if (!node.GetType().IsInstanceOf(_funcRetType))
		{
			String errorMessage = "Incompatible return type in expression and function deleration";
			if (_funcRetType.equals(new VoidType()))
				errorMessage = "Void function can't return a value";
			else if (node.GetType().equals(new VoidType()))
				errorMessage = "A non void function can't return empty value : 'return;'";
			
			errorMessage += LocationString();
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, errorMessage, node.GetInterval()));
		}
		return null;
	}
	
	public Object Visit(WhileStatement node) { return WhileIfValidator("While", node.GetExpression(), node); }	
	public Object Visit(IfStatement node) {	return  WhileIfValidator("If", node.GetIfExpression(), node); }
	
	private Object WhileIfValidator(String whatIsIt, Expression expr, ASTNode node)
	{
		if (!expr.GetType().IsInstanceOf(new IC.SymbolTables.Types.BasicType(BasicTypeEnum.BOOL)))
			ErrorStack.Push(
					new ErrorStringIntervaled(
							ErrorType.Semantic, 
							String.format("%s condition must be of a type boolean %s", whatIsIt, LocationString()), 
							expr.GetInterval()));
		return null;
	}
	
	public Object Visit(VirtualFunctionCall node) { return FunctionCallVisit(node); }
	public Object Visit(StaticFunctionCall node) { return FunctionCallVisit(node); }
	
	private Object FunctionCallVisit(FunctionCall func)
	{
		if(!func.WasFound())
			return null;
		
		if(func.GetParams().size() != ((MethodType)func.GetType()).GetParamTypes().length)
			return ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Can't invoke function '%s', wrong number of arguments, expected '%d', recived '%d' %s", 
							func.GetFunctionName(), ((MethodType)func.GetType()).GetParamTypes().length, func.GetParams().size(), LocationString()), 
					func.GetInterval()));
		
		CompareFuncParamsWithFormals(func);
		
		return null;
	}
	
	private void CompareFuncParamsWithFormals(FunctionCall func)
	{
		int i = 0;
		for(Expression got : func.GetParams())
			if (!got.GetType().IsInstanceOf(((MethodType)func.GetType()).GetParamTypes()[i++]))
				ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
						String.format("Incompatible assigment between expression type to parameter type in function = '%s', parameter number '%d' %s", 
								func.GetFunctionName(), i, LocationString()), 
						got.GetInterval()));
	}

	public Object Visit(BinaryOperationExpression node)
	{
		if(!CheckEquivalence(node))
			return null;
		if(!CheckIntOperators(node))
			return null;
		if(!CheckStringOperator(node))
			return null;
		if(!CheckIntEquivalence(node))
			return null;
		if(!CheckBooleanOperator(node))
			return null;
		return null;
	}
	
	private boolean CheckEquivalence(BinaryOperationExpression node)
	{
		if (!IsOneOfThoseOperators(node.GetOperation(),
				BinaryOperationExpression.Operation.Equal, 
				BinaryOperationExpression.Operation.NotEqual))
			return true;
		
		if (node.GetFirstExp().GetType().IsInstanceOf(node.GetSecondExp().GetType()) ||
				node.GetSecondExp().GetType().IsInstanceOf(node.GetFirstExp().GetType()))
			return true;
		else
		{
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Can't use operator '%s' on non deriving types %s", 
							node.GetOperation().toString(), LocationString()),
					node.GetInterval()));
			return false;
		}
	}
	
	private boolean CheckIntOperators(BinaryOperationExpression node)
	{
		//check if it is or Sub or Div or Mult or Mod
		if (!(IsAddOnInts(node.GetOperation(), node.GetType())
				||
					IsOneOfThoseOperators(node.GetOperation(),
						BinaryOperationExpression.Operation.Sub,
						BinaryOperationExpression.Operation.Div,
						BinaryOperationExpression.Operation.Mult,
						BinaryOperationExpression.Operation.Mod)))
			return true;
		
		if (AreBothExpressionIntegers(node))
			return true;
		else
		{
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Can't use operator '%s' on none integer types %s", 
							node.GetOperation().toString(), LocationString()),
					node.GetInterval()));
			return false;
		}
	}
	
	private boolean IsAddOnInts(BinaryOperationExpression.Operation operator, IC.SymbolTables.Types.Type expectedType)
	{
		return IsOneOfThoseOperators(operator, BinaryOperationExpression.Operation.Add)
				&& 
					expectedType.IsInstanceOf(
							new IC.SymbolTables.Types.BasicType(
									IC.SymbolTables.Types.BasicType.BasicTypeEnum.INT));
	}
	
	private boolean CheckStringOperator(BinaryOperationExpression node)
	{
		//check if it is a plus and we expect it to be on strings
		if(!(IsOneOfThoseOperators(node.GetOperation(), BinaryOperationExpression.Operation.Add)
				&& node.GetType().IsInstanceOf(new IC.SymbolTables.Types.BasicType(
						IC.SymbolTables.Types.BasicType.BasicTypeEnum.STRING))))
			return true;
		
		if (AreBothExpressionStrings(node))
			return true;
		else
		{
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Can't use operator '%s' on a string and some other type %s", 
							node.GetOperation().toString(), LocationString()),
					node.GetInterval()));
			return false;
		}
		
	}
	
	private boolean CheckIntEquivalence(BinaryOperationExpression node)
	{
		// is it More or MoreOrEqual or Less or LessOrEqual
		if (!IsOneOfThoseOperators(node.GetOperation(),
				BinaryOperationExpression.Operation.More,
				BinaryOperationExpression.Operation.MoreOrEqual,
				BinaryOperationExpression.Operation.Less,
				BinaryOperationExpression.Operation.LessOrEqual))
			return true;
		
		if (AreBothExpressionIntegers(node))
			return true;
		else
		{
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Can't use oeprator '%s' on none integers types %s", 
							node.GetOperation().toString(), LocationString()),
					node.GetInterval()));
			return false;
		}
		
	}
	
	private boolean CheckBooleanOperator(BinaryOperationExpression node)
	{
		// is And or OR
		if (!IsOneOfThoseOperators(node.GetOperation(),
				BinaryOperationExpression.Operation.And,
				BinaryOperationExpression.Operation.Or))
			return true;
		
		if(AreBothExpressionBooleans(node))
			return true;
		else
		{
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Can't use operator '%s' on none boolean types %s", 
							node.GetOperation().toString(), LocationString()),
					node.GetInterval()));
			return false;
		}
	}
	
	private boolean IsOneOfThoseOperators(BinaryOperationExpression.Operation operator, BinaryOperationExpression.Operation... ops)
	{
		boolean ret = false;
		for (BinaryOperationExpression.Operation op : ops)
			ret |= operator.equals(op);
		return ret;
	}
	
	private boolean AreBothExpressionIntegers(BinaryOperationExpression node)
	{
		return CheckBothTypes(node, IC.SymbolTables.Types.BasicType.BasicTypeEnum.INT);
	}
	
	private boolean AreBothExpressionStrings(BinaryOperationExpression node)
	{
		return CheckBothTypes(node, IC.SymbolTables.Types.BasicType.BasicTypeEnum.STRING);
	}
	
	private boolean AreBothExpressionBooleans(BinaryOperationExpression node)
	{
		return CheckBothTypes(node ,IC.SymbolTables.Types.BasicType.BasicTypeEnum.BOOL);
	}
	
	private boolean CheckBothTypes(BinaryOperationExpression node, IC.SymbolTables.Types.BasicType.BasicTypeEnum type)
	{
		return (node.GetFirstExp().GetType().IsInstanceOf(new IC.SymbolTables.Types.BasicType(type)) &&
			node.GetSecondExp().GetType().IsInstanceOf(new IC.SymbolTables.Types.BasicType(type)));
	}
	
	public Object Visit(UnaryOperationExpression node)
	{
		if (!node.GetType().equals(node.GetExpression().GetType()))
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
				String.format("Can't invoke operator '%s' an a none %s %s", 
						node.GetOperation().toString(), node.GetOperation().GetType().toString(), LocationString()), 
				node.GetInterval()));
		
		return null;
	}
	
	public Object Visit(NewArrayExpression node)
	{
		if (!(node.GetSizeExpression().GetType().IsInstanceOf(new IC.SymbolTables.Types.BasicType(BasicTypeEnum.INT))))
			return ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Illegal new array expression, size parameter must be of type int %s", LocationString()), 
					node.GetInterval()));
		return null; 	
	}
	
	public Object Visit(ArrayLocation node)
	{
		if (!(node.GetIndex().GetType().IsInstanceOf(new IC.SymbolTables.Types.BasicType(BasicTypeEnum.INT))))
			return ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Illegal array index: non-integer type %s", LocationString()), 
					node.GetInterval()));
		return null; 	
	}
	
	public Object Visit(LengthExpression node)
	{ 
		if (!(node.GetExpression().GetType() instanceof IC.SymbolTables.Types.ArrayType))
			return ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
					String.format("Can't use length expretion on a none array type %s", LocationString()), 
					node.GetInterval()));
		return null; 
	}
	
	private String LocationString()
	{
		return ((_classID == null) || (_funcName == null)) 
			?
				""
			:
				String.format(", in function = '%s', in class = '%s'", _funcName, _classID);
	}
}
